﻿using System;

namespace Core
{
    public class SHA
    {
        private static byte firstPad = 128;
        private static byte secondPad = 0;

        public static string doSHA1(byte[] bInput)
        {
            // initialize variable
            uint h0 = 0x67452301;
            uint h1 = 0xEFCDAB89;
            uint h2 = 0x98BADCFE;
            uint h3 = 0x10325476;
            uint h4 = 0xC3D2E1F0;
            uint a, b, c, d, e;
            uint f, k, temp;
            string sh0, sh1, sh2, sh3, sh4;

            // padding di sini
            bInput = padding(bInput);

            // ubah ke uint[] di sini

            uint[] uiArray = ToArrayUint(bInput);


            int numChunk = uiArray.Length / 16;
            if (uiArray.Length % 16 != 0) numChunk++;

            for (int i = 0; i < numChunk; ++i)
            {
                uint[] w = new uint[80];
                int j = 0;
                for (j = 0; j < 16; ++j)
                {
                    w[j] = uiArray[(16 * i) + j];
                    w[j] = ReverseBytes(w[j]);
                    //Console.WriteLine(Convert.BytesToString(w[j], 16));
                }

                for (; j < 80; ++j)
                {
                    w[j] = rotateLeft((w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16]), 1);
                }

                a = h0;
                b = h1;
                c = h2;
                d = h3;
                e = h4;
                for (int l = 0; l < 80; ++l)
                {
                    if (0 <= l && l < 20)
                    {
                        f = (b & c) | ((~b) & d);
                        k = 0x5A827999;
                    }
                    else if (20 <= l && l < 40)
                    {
                        f = b ^ c ^ d;
                        k = 0x6ED9EBA1;
                    }
                    else if (40 <= l && l < 60)
                    {
                        f = (b & c) | (b & d) | (c & d);
                        k = 0x8F1BBCDC;
                    }
                    else //if (60 <= l && l < 80)
                    {
                        f = b ^ c ^ d;
                        k = 0xCA62C1D6;
                    }
                    temp = (rotateLeft(a, 5) + f + e + k + w[l]);
                    e = d;
                    d = c;
                    c = rotateLeft(b, 30);
                    b = a;
                    a = temp;


                }
                h0 = (h0 + a);
                h1 = (h1 + b);
                h2 = (h2 + c);
                h3 = (h3 + d);
                h4 = (h4 + e);

            }
            sh0 = Convert.ToString(h0, 16);
            sh1 = Convert.ToString(h2, 16);
            sh2 = Convert.ToString(h2, 16);
            sh3 = Convert.ToString(h3, 16);
            sh4 = Convert.ToString(h4, 16);

            return sh0 + " " + sh1 + " " + sh2 + " " + sh3 + " " + sh4;
        }

        private static byte[] padding(byte[] bInput)
        {
            UInt64 length = getLength(bInput);
            length = ReverseBytes(length);
            byte[] byteLength = ToByteArray(length);

            bInput = concat(bInput, firstPad);
            while (bInput.Length % 64 != 56)
            {
                bInput = concat(bInput, secondPad);
            }
            bInput = concat(bInput, byteLength);

            return bInput;
        }

        private static UInt64 getLength(byte[] bInput)
        {
            return (UInt64)(bInput.Length * 8);
        }

        private static byte[] ToByteArray(UInt64 n)
        {
            return BitConverter.GetBytes(n);
        }

        private static void reverse(byte[] b)
        {

        }

        private static uint[] ToArrayUint(byte[] bInput)
        {
            uint[] retval = new uint[bInput.Length / 4];
            for (int i = 0; i < retval.Length; ++i)
            {
                retval[i] = BitConverter.ToUInt32(bInput, i * 4);
            }
            return retval;
            //       throw new NotImplementedException();
        }

        private static byte[] concat(byte[] a, byte[] b)
        {
            byte[] retval = new byte[a.Length + b.Length];
            int i = 0;
            for (i = 0; i < a.Length; ++i)
            {
                retval[i] = a[i];
            }
            int j = 0;
            for (; i < retval.Length; ++i)
            {
                retval[i] = b[j];
                ++j;
            }

            return retval;
        }

        private static byte[] concat(byte[] a, byte b)
        {
            byte[] retval = new byte[a.Length + 1];
            int i = 0;
            for (i = 0; i < a.Length; ++i)
            {
                retval[i] = a[i];
            }
            for (; i < retval.Length; ++i)
            {
                retval[i] = b;
            }

            return retval;
        }

        /// <summary>
        /// Rotate left by shift of uiValue
        /// </summary>
        /// <param name="uiValue"></param>
        /// <param name="shift"></param>
        /// <returns></returns>
        public static uint rotateLeft(uint uiValue, int shift)
        {
            if ((shift &= 31) == 0)
                return uiValue;
            return (uiValue << shift) | (uiValue >> (32 - shift));
        }

        public static UInt32 ReverseBytes(UInt32 value)
        {
            return (value & 0x000000FFU) << 24 | (value & 0x0000FF00U) << 8 |
                   (value & 0x00FF0000U) >> 8 | (value & 0xFF000000U) >> 24;
        }

        public static UInt64 ReverseBytes(UInt64 value)
        {
            return (value & 0x00000000000000FFUL) << 56 | (value & 0x000000000000FF00UL) << 40 |
                   (value & 0x0000000000FF0000UL) << 24 | (value & 0x00000000FF000000UL) << 8 |
                   (value & 0x000000FF00000000UL) >> 8 | (value & 0x0000FF0000000000UL) >> 24 |
                   (value & 0x00FF000000000000UL) >> 40 | (value & 0xFF00000000000000UL) >> 56;
        }
    }
}
